Se construye un repositorio en github con las pruebas de concepto y un proyecto streamlit para que un usuario pueda cargar una imagen y visualizar la paleta y la visualización PCA.
- Repositorio: https://github.com/06danielsms/micro_project_one/tree/main
- Se despliega la aplicacion en la siguiente url: https://microprojectone-mlns-6sxn6bsflr9atntheta8cd.streamlit.app
# Se cargan las librerías
import numpy as np
import os
import cv2
import matplotlib.pyplot as plt
import re
import pandas as pd
import random
import webcolors
from sklearn.cluster import KMeans, MeanShift, estimate_bandwidth
from sklearn.metrics import silhouette_score, silhouette_samples
from tqdm import tqdm
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
# Función para cargar imágenes desde un directorio dado, construido con una muestra de 100 imáges, de las que aleatoriamente se toman en el procesamiento la cantidad definida, para este caso 5, script(https://github.com/06danielsms/micro_project_one/blob/d98d165bc5860119aa03e3a9a3b01382199c0890/scripts/select_sample.py)
def load_images(root_path):
files = os.listdir(root_path)
random.shuffle(files)
images_original = []
for file in files:
img = cv2.imread(os.path.join(root_path, file))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
images_original.append({'image': img, 'name': file})
return images_original
# Función para preprocesar las imágenes (escalar, normalizar, cambiar tamaño)
def pre_process_image(images_original, flatten=False, normalize=False, target_size=None):
images_process = []
for data in images_original:
height, width = data['image'].shape[:2]
if target_size is not None:
ratio = float(target_size) / max(height, width)
new_height = int(height * ratio)
new_width = int(width * ratio)
image = cv2.resize(data['image'], (new_width,new_height))
if flatten:
image = image.reshape((-1, 3))
if normalize:
image = image / 255.0
images_process.append(image)
return images_process
# Función para aplicar el algoritmo de K-means a una imagen
def kmeans_image(img,num_clusters):
kmeans = KMeans(n_clusters=num_clusters,random_state=42)
kmeans.fit(img)
return kmeans.cluster_centers_, kmeans.labels_
# Función para convertir un color RGB a su código hexadecimal
def rgb_to_hex(rgb):
return '#{:02x}{:02x}{:02x}'.format(int(rgb[0]), int(rgb[1]), int(rgb[2]))
# Función para aplicar PCA o t-SNE a una imagen y visualizarla
def dimensionality_reduction(img, colors, tsne=False,p=3):
if not tsne:
pca = PCA(n_components=2)
x_train_reduced = pca.fit_transform(img)
else:
tsne = TSNE(n_components=2, learning_rate='auto',init='random', perplexity=1)
x_train_reduced = tsne.fit_transform(img)
print('>>',x_train_reduced.shape)
colors = colors * 255
colors = list(map(rgb_to_hex,colors))
color_dict = {}
for i in colors:
color_dict[i] = i
color_dict
colors = np.array(colors)
for category in np.unique(colors):
mask = colors == category
plt.scatter(x_train_reduced[:,0][mask], x_train_reduced[:,1][mask], label=category, color=color_dict[category],edgecolors='black')
plt.xlabel('X-axis Label')
plt.ylabel('Y-axis Label')
plt.title('Scatter Plot with Categories')
plt.legend()
plt.show()
return x_train_reduced
# Función para etiquetar el color con hex
def rgb_to_color_hex(rgb_tuple):
try:
color_name = webcolors.rgb_to_hex(rgb_tuple)
except ValueError:
color_name = 'Unknown'
return color_name
# Función para visualizar la paleta de colores de una imagen junto con sus colores predominantes
def draw_image_palette(image_original, centroids=None):
palette = centroids * 255
title = image_original['name'][:-3]
title = re.sub(r'[^A-Za-z0-9\-]+', ' ', title).upper()
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title(f"{title}")
plt.imshow(image_original['image'])
plt.axis('off')
if centroids is not None:
plt.subplot(1, 2, 2)
plt.title('Palette')
palette_colors = []
color_names = []
for color in palette:
palette_colors.append(color)
color_name = rgb_to_color_hex(tuple(color.astype(int)))
color_names.append(color_name)
plt.imshow(np.expand_dims(palette_colors, axis=0).astype(np.uint8))
for i, name in enumerate(color_names):
plt.text(i, .5, name, ha='center', va='top', color='black', fontsize=8)
plt.axis('off')
plt.tight_layout()
plt.show()
# Función para visualizar una serie de colores
def plot_colors(colors):
plt.figure(figsize=(8, 6))
for i in range(len(colors)):
color_swatch = np.zeros((100, 100, 3))
color_swatch[:, :, :] = colors[i]
plt.subplot(1, len(colors), i + 1)
plt.imshow(color_swatch)
plt.axis('off')
plt.show()
# Función para el método del codo
def elbow_method(data, max_clusters=10):
distortions = []
for i in range(1, max_clusters + 1):
kmeans = KMeans(n_clusters=i, random_state=42)
kmeans.fit(data)
distortions.append(kmeans.inertia_)
# Plotting the elbow method graph
plt.plot(range(1, max_clusters + 1), distortions, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.title('Elbow Method')
plt.show()
# Función para el análisis de silueta
def silhouette_analysis(data, max_clusters=10):
silhouette_scores = []
for i in range(2, max_clusters + 1):
kmeans = KMeans(n_clusters=i, random_state=42)
cluster_labels = kmeans.fit_predict(data)
silhouette_avg = silhouette_score(data, cluster_labels)
silhouette_scores.append(silhouette_avg)
# Plotting silhouette scores
plt.plot(range(2, max_clusters + 1), silhouette_scores, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Analysis')
plt.show()
# Función para graficar el coeficiente de silueta para diferentes números de clusters
def silhouette_plot(data, max_clusters=10):
scores = []
for i in range(2, max_clusters + 1):
model_k = KMeans(n_clusters=i, n_init=10, random_state=42)
model_k.fit(data)
score = silhouette_score(data, model_k.labels_)
scores.append(score)
display(pd.DataFrame({'K': range(2, max_clusters+1), 'Coeficiente': scores}))
plt.plot(range(2, max_clusters+1), scores, marker='o')
plt.xlabel('Número de clústeres')
plt.ylabel('Silhouette Score')
plt.grid()
plt.show()
root_path = '/home/satoru/repos/u_andes/maia/mlns/micro_projects/one/sample'
# root_path = '../../project_images/sample'
images_original = load_images(root_path)
images_process = pre_process_image(images_original, flatten=True, normalize=True, target_size=100)
num_clusters = 7
num_images = 5
processed_images = 0
for i, image in enumerate(images_process):
print(f"****************************************{i}************************************")
centroids,labels = kmeans_image(images_process[i],num_clusters)
draw_image_palette(images_original[i], centroids)
# plot_colors(centroids)
x_train_reduced = dimensionality_reduction(images_process[i],centroids[labels])
elbow_method(x_train_reduced)
silhouette_plot(x_train_reduced)
processed_images += 1
if processed_images >= num_images:
break
****************************************0************************************
>> (7500, 2)
| K | Coeficiente | |
|---|---|---|
| 0 | 2 | 0.559934 |
| 1 | 3 | 0.470101 |
| 2 | 4 | 0.409273 |
| 3 | 5 | 0.377827 |
| 4 | 6 | 0.351288 |
| 5 | 7 | 0.351718 |
| 6 | 8 | 0.348934 |
| 7 | 9 | 0.356604 |
| 8 | 10 | 0.353961 |
****************************************1************************************
>> (8900, 2)
| K | Coeficiente | |
|---|---|---|
| 0 | 2 | 0.556825 |
| 1 | 3 | 0.518262 |
| 2 | 4 | 0.465134 |
| 3 | 5 | 0.418143 |
| 4 | 6 | 0.418087 |
| 5 | 7 | 0.408511 |
| 6 | 8 | 0.403810 |
| 7 | 9 | 0.398678 |
| 8 | 10 | 0.387860 |
****************************************2************************************
>> (8100, 2)
| K | Coeficiente | |
|---|---|---|
| 0 | 2 | 0.669156 |
| 1 | 3 | 0.535710 |
| 2 | 4 | 0.488387 |
| 3 | 5 | 0.458307 |
| 4 | 6 | 0.485979 |
| 5 | 7 | 0.465583 |
| 6 | 8 | 0.432839 |
| 7 | 9 | 0.437427 |
| 8 | 10 | 0.423389 |
****************************************3************************************
>> (7800, 2)
| K | Coeficiente | |
|---|---|---|
| 0 | 2 | 0.678956 |
| 1 | 3 | 0.588616 |
| 2 | 4 | 0.629390 |
| 3 | 5 | 0.599650 |
| 4 | 6 | 0.514880 |
| 5 | 7 | 0.488601 |
| 6 | 8 | 0.462438 |
| 7 | 9 | 0.464981 |
| 8 | 10 | 0.448865 |
****************************************4************************************
>> (9400, 2)
| K | Coeficiente | |
|---|---|---|
| 0 | 2 | 0.974185 |
| 1 | 3 | 0.782770 |
| 2 | 4 | 0.786050 |
| 3 | 5 | 0.784894 |
| 4 | 6 | 0.582848 |
| 5 | 7 | 0.579965 |
| 6 | 8 | 0.578600 |
| 7 | 9 | 0.578760 |
| 8 | 10 | 0.622316 |
Analisis con un grupo fijo de 100 imágenes¶
data = pd.read_csv('../siloute_100_100.csv')
data.describe()
| C_2 | C_3 | C_4 | C_5 | C_6 | C_7 | C_8 | C_9 | C_10 | |
|---|---|---|---|---|---|---|---|---|---|
| count | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 |
| mean | 0.613901 | 0.542227 | 0.508238 | 0.478054 | 0.454096 | 0.438785 | 0.422136 | 0.412594 | 0.402151 |
| std | 0.110287 | 0.101184 | 0.094951 | 0.088007 | 0.084038 | 0.078515 | 0.073808 | 0.072973 | 0.071786 |
| min | 0.413652 | 0.323174 | 0.358966 | 0.328188 | 0.297365 | 0.292673 | 0.288811 | 0.287857 | 0.286479 |
| 25% | 0.536288 | 0.479066 | 0.447793 | 0.420521 | 0.401077 | 0.384523 | 0.369392 | 0.359340 | 0.355356 |
| 50% | 0.603706 | 0.520766 | 0.495410 | 0.465566 | 0.429557 | 0.430741 | 0.410825 | 0.396351 | 0.383408 |
| 75% | 0.684022 | 0.590583 | 0.552592 | 0.517165 | 0.485914 | 0.466015 | 0.457353 | 0.448227 | 0.431120 |
| max | 0.973359 | 0.840561 | 0.804941 | 0.828541 | 0.813768 | 0.789711 | 0.680050 | 0.677156 | 0.671437 |
fig, ax = plt.subplots()
vp = ax.violinplot(data[['C_2','C_3','C_4','C_5','C_6','C_7','C_8']], [0,2, 4, 6,8,10,12], widths=2,
showmeans=False, showmedians=False, showextrema=False)
data[['C_2','C_3','C_4','C_5','C_6','C_7','C_8','C_9','C_10']].idxmax(axis=1).value_counts()
C_2 84 C_3 6 C_4 5 C_5 3 C_7 1 C_6 1 Name: count, dtype: int64
data[['C_5','C_6','C_7','C_8','C_9','C_10']].idxmax(axis=1).value_counts()
C_5 78 C_6 14 C_7 6 C_8 2 Name: count, dtype: int64
Realizando un análisis del rendimiento con la métrica de score silhouette se evidencia que tomando desde un k de 2 tiene el mejor rendimiento por lo general en grafica y al contar el máximo de cada fila también ek k de 2 es el cual mayor frecuencia se presenta.
Pero no tiene mucho sentido solo tener 2 colores. Por lo que se pudiese decir que a menor K tendrá mejor rendimiento de partición. Entonces si como restricción se tuviera un rango de número de colores deseados para mantener la mayor partición de los datos se debería de escoger el número menor en casos generales.
Selección de Algoritmo¶
Nos decidimos por usar K-means debido a que en primer lugar para la tarea se requería un algoritmo que genera centroides que serían los colores de la paleta. Después se descartó Meanshift debido al coste computacional y los colores que generaba como centroides no se veían acordes a la imagen. K-medoides es costoso computacionalmente, por lo cual se pudiese solucionar al hacer reducción de la imagen. Por consiguiente K-means es el que genera mejor resultado visual y tiene menos costo computacional